package server

import (
	"context"
	"fmt"
	"go-caipu/pkg/i18n"
	"go-caipu/pkg/install"
	"go-caipu/pkg/setting"
	"net/http"
	_ "net/http/pprof"

	"os"
	"os/signal"
	"runtime"
	"runtime/debug"
	"runtime/trace"
	"syscall"

	"time"

	"github.com/spf13/cobra"

	"go-caipu/pkg/api"
	"go-caipu/pkg/infra/log"
	"go-caipu/pkg/server"
)

var version = "1.0.0"
var commit = "NA"
var buildBranch = "main"
var (
	config   string
	homePath string
	pidfile  string
	//监控信息
	profile     bool
	profileAddr string
	profilePort uint64
	tracing     bool
	tracingFile string
)
var SerCmd = &cobra.Command{
	Use:     "server",
	Version: version,
	Short:   " start go-caipu server System",
	Long:    " start go-caipu server System",
	Run: func(cmd *cobra.Command, args []string) {
		RunServer()
	},
}

func init() {
	//判断dev文件是否存在,优先读取dev文件
	configFile := "config/default-dev.yml"
	if _, err := os.Stat(configFile); err != nil {
		configFile = "config/default.yml"
	}
	SerCmd.PersistentFlags().StringVar(&config, "config", configFile, "Path to config file")
	SerCmd.PersistentFlags().StringVar(&homePath, "homepath", "", "Path to go-caipu install/site path, defaults to working directory")
	SerCmd.PersistentFlags().StringVar(&pidfile, "pidfile", "c", "Path to go-CaiPu pid file")

	SerCmd.PersistentFlags().BoolVar(&profile, "profile", false, "Turn on pprof profiling")
	SerCmd.PersistentFlags().StringVar(&profileAddr, "profile-addr", "localhost", "Define custom address for profiling")
	SerCmd.PersistentFlags().Uint64Var(&profilePort, "profile-port", 6060, "Define custom port for profiling")
	SerCmd.PersistentFlags().BoolVar(&tracing, "tracing", false, "Turn on tracing")
	SerCmd.PersistentFlags().StringVar(&tracingFile, "tracing-file", "trace.out", "Define tracing output file")
}
func RunServer() error {

	cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{Config: config, HomePage: homePath})
	//系统未配置则首先引导进行系统配置
	if !cfg.Application.Installed {
		install.Init(cfg, config)
		return nil
	}

	profileDiagnostics := newProfilingDiagnostics(profile, profileAddr, profilePort)

	if err := profileDiagnostics.overrideWithEnv(); err != nil {
		return err
	}

	traceDiagnostics := newTracingDiagnostics(tracing, tracingFile)
	if err := traceDiagnostics.overrideWithEnv(); err != nil {
		return err
	}

	//start profile server
	if profileDiagnostics.enabled {
		fmt.Println("diagnostics: pprof profiling enabled", "addr", profileDiagnostics.addr, "port", profileDiagnostics.port)
		runtime.SetBlockProfileRate(1)
		go func() {
			// TODO: We should enable the linter and fix G114 here.
			//	G114: Use of net/http serve function that has no support for setting timeouts (gosec)
			//
			//nolint:gosec
			err := http.ListenAndServe(fmt.Sprintf("%s:%d", profileDiagnostics.addr, profileDiagnostics.port), nil)
			if err != nil {
				panic(err)
			}
		}()
	}

	defer func() {
		if err := log.Close(); err != nil {
			fmt.Fprintf(os.Stderr, "Failed to close log: %s\n", err)
		}
	}()

	clilog := log.New("caipu.server")

	defer func() {
		// If we've managed to initialize them, this is the last place
		// where we're able to log anything that'll end up in Grafana's
		// log files.
		// Since operators are not always looking at stderr, we'll try
		// to log any and all panics that are about to crash Grafana to
		// our regular log locations before exiting.
		if r := recover(); r != nil {
			reason := fmt.Sprintf("%v", r)
			clilog.Error("Critical error", "reason", reason, "stackTrace", string(debug.Stack()))
			panic(r)
		}
	}()
	//create trace file
	if traceDiagnostics.enabled {
		fmt.Println("diagnostics: tracing enabled", "file", traceDiagnostics.file)
		f, err := os.Create(traceDiagnostics.file)
		if err != nil {
			panic(err)
		}
		defer func() {
			if err := f.Close(); err != nil {
				clilog.Error("Failed to write trace diagnostics", "path", traceDiagnostics.file, "err", err)
			}
		}()

		if err := trace.Start(f); err != nil {
			panic(err)
		}
		defer trace.Stop()
	}
	s, err := server.Initialize(
		setting.CommandLineArgs{
			Config:   config,
			HomePage: homePath,
		},
		server.Options{
			Version:     version,
			Commit:      commit,
			BuildBranch: buildBranch,
		},
		api.ServerOptions{})
	if err != nil {
		return err
	}
	ctx := context.Background()
	go listenToSystemSignals(ctx, s)

	i18n.Init()
	return s.Run()
}

func listenToSystemSignals(ctx context.Context, s *server.Server) {
	signalChan := make(chan os.Signal, 1)
	sighupChan := make(chan os.Signal, 1)

	signal.Notify(sighupChan, syscall.SIGHUP)
	signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)

	for {
		select {
		case <-sighupChan:
			if err := log.Reload(); err != nil {
				fmt.Fprintf(os.Stderr, "Failed to reload loggers: %s\n", err)
			}
		case sig := <-signalChan:
			ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
			defer cancel()
			if err := s.Shutdown(ctx, fmt.Sprintf("System signal: %s", sig)); err != nil {
				fmt.Fprintf(os.Stderr, "Timed out waiting for server to shut down\n")
			}
			return
		}
	}
}
